#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
文件清理模块 - 负责清理重复文件、空文件夹和临时文件
"""
import os
import hashlib
from pathlib import Path
from typing import Dict, List, Set, Tuple, Union, Optional
import shutil

def find_duplicates(directory: Union[str, Path], recursive: bool = True) -> Dict[str, List[Path]]:
    """
    查找目录中的重复文件
    
    Args:
        directory: 要搜索的目录
        recursive: 是否递归搜索子目录
        
    Returns:
        Dict[str, List[Path]]: 重复文件的映射，键为文件哈希值，值为重复文件路径列表
    """
    directory_path = Path(directory)
    if not directory_path.exists() or not directory_path.is_dir():
        raise ValueError(f"目录不存在或不是文件夹: {directory}")
    
    # 存储文件大小到文件路径的映射
    size_map: Dict[int, List[Path]] = {}
    # 存储文件哈希值到文件路径的映射
    hash_map: Dict[str, List[Path]] = {}
    
    # 获取所有文件
    if recursive:
        files = [f for f in directory_path.rglob("*") if f.is_file()]
    else:
        files = [f for f in directory_path.iterdir() if f.is_file()]
    
    # 首先按文件大小分组（相同大小的文件可能是重复的）
    for file_path in files:
        try:
            file_size = file_path.stat().st_size
            if file_size == 0:  # 跳过空文件
                continue
                
            if file_size in size_map:
                size_map[file_size].append(file_path)
            else:
                size_map[file_size] = [file_path]
        except (PermissionError, OSError) as e:
            print(f"无法访问文件 {file_path}: {e}")
    
    # 只处理有多个文件的大小组
    potential_duplicates = {size: paths for size, paths in size_map.items() if len(paths) > 1}
    
    # 对潜在的重复文件计算哈希值
    for _, file_paths in potential_duplicates.items():
        for file_path in file_paths:
            try:
                file_hash = calculate_file_hash(file_path)
                if file_hash in hash_map:
                    hash_map[file_hash].append(file_path)
                else:
                    hash_map[file_hash] = [file_path]
            except (PermissionError, OSError) as e:
                print(f"无法计算文件哈希值 {file_path}: {e}")
    
    # 只返回有重复的文件（多个文件具有相同的哈希值）
    return {file_hash: paths for file_hash, paths in hash_map.items() if len(paths) > 1}

def calculate_file_hash(file_path: Union[str, Path], algorithm: str = "md5", chunk_size: int = 8192) -> str:
    """
    计算文件的哈希值
    
    Args:
        file_path: 文件路径
        algorithm: 哈希算法，默认为md5
        chunk_size: 每次读取的块大小，默认为8KB
        
    Returns:
        str: 文件的哈希值（十六进制字符串）
    """
    path = Path(file_path)
    
    if algorithm == "md5":
        hasher = hashlib.md5()
    elif algorithm == "sha1":
        hasher = hashlib.sha1()
    elif algorithm == "sha256":
        hasher = hashlib.sha256()
    else:
        raise ValueError(f"不支持的哈希算法: {algorithm}")
    
    with path.open("rb") as f:
        chunk = f.read(chunk_size)
        while chunk:
            hasher.update(chunk)
            chunk = f.read(chunk_size)
    
    return hasher.hexdigest()

def remove_duplicates(duplicates: Dict[str, List[Path]], keep_strategy: str = "oldest") -> Dict[str, List[Path]]:
    """
    移除重复文件
    
    Args:
        duplicates: 重复文件映射（由find_duplicates返回）
        keep_strategy: 保留文件的策略，可选：
            - "oldest": 保留最旧的文件
            - "newest": 保留最新的文件
            - "first": 保留第一个文件（路径字母顺序）
            
    Returns:
        Dict[str, List[Path]]: 已删除的文件映射
    """
    deleted_files: Dict[str, List[Path]] = {}
    
    for file_hash, file_paths in duplicates.items():
        if len(file_paths) <= 1:
            continue
            
        # 根据保留策略确定要保留的文件
        if keep_strategy == "oldest":
            # 按创建时间排序，保留最早创建的
            sorted_files = sorted(file_paths, key=lambda p: p.stat().st_ctime)
        elif keep_strategy == "newest":
            # 按创建时间排序，保留最后创建的
            sorted_files = sorted(file_paths, key=lambda p: p.stat().st_ctime, reverse=True)
        else:  # "first" 或其他任何值
            # 按路径字母顺序排序
            sorted_files = sorted(file_paths)
        
        # 保留第一个文件，删除其余的
        keep_file = sorted_files[0]
        files_to_delete = sorted_files[1:]
        
        deleted_paths = []
        for file_path in files_to_delete:
            try:
                file_path.unlink()  # 删除文件
                deleted_paths.append(file_path)
            except (PermissionError, OSError) as e:
                print(f"无法删除文件 {file_path}: {e}")
        
        if deleted_paths:
            deleted_files[file_hash] = deleted_paths
    
    return deleted_files

def find_empty_dirs(directory: Union[str, Path], recursive: bool = True) -> List[Path]:
    """
    查找空文件夹
    
    Args:
        directory: 要搜索的目录
        recursive: 是否递归搜索子目录
        
    Returns:
        List[Path]: 空文件夹路径列表
    """
    directory_path = Path(directory)
    if not directory_path.exists() or not directory_path.is_dir():
        raise ValueError(f"目录不存在或不是文件夹: {directory}")
    
    empty_dirs = []
    
    # 如果不递归，只检查顶级子目录
    if not recursive:
        for dir_path in directory_path.iterdir():
            if dir_path.is_dir() and not any(dir_path.iterdir()):
                empty_dirs.append(dir_path)
        return empty_dirs
    
    # 递归搜索所有子目录（自下而上，以便正确识别嵌套的空目录）
    for root, dirs, files in os.walk(directory_path, topdown=False):
        root_path = Path(root)
        # 目录为空的条件：没有文件且没有子目录（或所有子目录都是空的且已被删除）
        if not files and not any(root_path.iterdir()):
            # 不包括根目录本身
            if root_path != directory_path:
                empty_dirs.append(root_path)
    
    return empty_dirs

def remove_empty_dirs(directory: Union[str, Path], recursive: bool = True) -> List[Path]:
    """
    删除空文件夹
    
    Args:
        directory: 要清理的目录
        recursive: 是否递归处理子目录
        
    Returns:
        List[Path]: 已删除的空文件夹列表
    """
    empty_dirs = find_empty_dirs(directory, recursive)
    deleted_dirs = []
    
    for dir_path in empty_dirs:
        try:
            dir_path.rmdir()  # 只删除空目录
            deleted_dirs.append(dir_path)
        except (PermissionError, OSError) as e:
            print(f"无法删除目录 {dir_path}: {e}")
    
    return deleted_dirs

def find_temp_files(directory: Union[str, Path], recursive: bool = True, 
                  temp_extensions: Optional[List[str]] = None) -> List[Path]:
    """
    查找临时文件
    
    Args:
        directory: 要搜索的目录
        recursive: 是否递归搜索子目录
        temp_extensions: 临时文件扩展名列表，如果为None则使用默认列表
        
    Returns:
        List[Path]: 临时文件路径列表
    """
    directory_path = Path(directory)
    if not directory_path.exists() or not directory_path.is_dir():
        raise ValueError(f"目录不存在或不是文件夹: {directory}")
    
    # 默认临时文件扩展名
    if temp_extensions is None:
        temp_extensions = [
            ".tmp", ".temp", ".bak", ".backup", ".swp", ".~", ".cache",
            "._mp", ".crdownload", ".part", ".partial", ".download",
            ".DS_Store", "Thumbs.db"
        ]
    
    temp_files = []
    
    # 将扩展名转换为小写以进行不区分大小写的比较
    temp_extensions = [ext.lower() for ext in temp_extensions]
    
    # 搜索匹配的文件
    if recursive:
        for ext in temp_extensions:
            temp_files.extend(list(directory_path.rglob(f"*{ext}")))
    else:
        for ext in temp_extensions:
            temp_files.extend(list(directory_path.glob(f"*{ext}")))
    
    return [f for f in temp_files if f.is_file()]

def remove_temp_files(directory: Union[str, Path], recursive: bool = True,
                     temp_extensions: Optional[List[str]] = None) -> List[Path]:
    """
    删除临时文件
    
    Args:
        directory: 要清理的目录
        recursive: 是否递归处理子目录
        temp_extensions: 临时文件扩展名列表，如果为None则使用默认列表
        
    Returns:
        List[Path]: 已删除的临时文件列表
    """
    temp_files = find_temp_files(directory, recursive, temp_extensions)
    deleted_files = []
    
    for file_path in temp_files:
        try:
            file_path.unlink()
            deleted_files.append(file_path)
        except (PermissionError, OSError) as e:
            print(f"无法删除临时文件 {file_path}: {e}")
    
    return deleted_files

def find_large_files(directory: Union[str, Path], min_size: int = 100*1024*1024, 
                    recursive: bool = True) -> List[Tuple[Path, int]]:
    """
    查找大文件
    
    Args:
        directory: 要搜索的目录
        min_size: 最小文件大小（字节），默认为100MB
        recursive: 是否递归搜索子目录
        
    Returns:
        List[Tuple[Path, int]]: 大文件路径和大小的元组列表，按大小降序排序
    """
    directory_path = Path(directory)
    if not directory_path.exists() or not directory_path.is_dir():
        raise ValueError(f"目录不存在或不是文件夹: {directory}")
    
    large_files = []
    
    # 获取所有文件
    if recursive:
        files = [f for f in directory_path.rglob("*") if f.is_file()]
    else:
        files = [f for f in directory_path.iterdir() if f.is_file()]
    
    # 查找大于指定大小的文件
    for file_path in files:
        try:
            file_size = file_path.stat().st_size
            if file_size >= min_size:
                large_files.append((file_path, file_size))
        except (PermissionError, OSError) as e:
            print(f"无法访问文件 {file_path}: {e}")
    
    # 按文件大小降序排序
    return sorted(large_files, key=lambda x: x[1], reverse=True) 